﻿using System;
using System.Linq;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// Implements the Common Western Music Notation <see cref="CustomSymbolData"/>.
    /// </summary>
    public class CWSymbolData : CustomSymbolData
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="CWSymbolData"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="myStaff">
        /// <para>
        /// Type: <see cref="StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The descriptor of the staff that presents the symbol.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ValidationException">
        /// <paramref name="myStaff"/> is <see langword="null"/>.<br/>
        /// -or-<br/>
        /// <paramref name="myStaff"/> does not belong to the part that owns the <paramref name="owner"/>.
        /// </exception>
        public CWSymbolData(CompositionPart packagePart, DurationalSymbolElement owner, StaffDescriptor myStaff)
            : base(packagePart, owner)
        {
            MyStaff = myStaff;
        }

        /// <summary>
        /// Creates a new <see cref="CWSymbolData"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        public CWSymbolData(CompositionPart packagePart, DurationalSymbolElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            myStaffAttribute = element.Attributes[PackageVocabulary.StaffDescriptorAttribute];

            var part = FindOwner<PartElement>().Descriptor;
            var cwData = part.FindCustomData<CWPartData>();
            var sid = new Guid(myStaffAttribute.Value);

            myStaff = cwData.Staves.FirstOrDefault(s => s.ID == sid);
            if (myStaff == null)
                throw new SerializationException("Could not find the staff ID " + sid + " in the descriptor of the owner part!", element.OuterXml);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.CWSymbolDataElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            myStaffAttribute = document.CreateAttribute(PackageVocabulary.StaffDescriptorAttribute);
            
            element.Attributes.Append(myStaffAttribute);

            return element;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the descriptor of the staff that present the symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="StaffDescriptor"/>
        /// </para>
        /// <para>
        /// The descriptor of the staff that present the symbol.<br/>
        /// The basic model does not have a notion of a staff. Hence, this value
        /// was introduced as an extension so that every <see cref="MusicSectionInterior"/>
        /// can present the symbol at the same staff as the others.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// <paramref name="value"/> is <see langword="null"/>.<br/>
        /// -or-<br/>
        /// The <paramref name="value"/> does not belong to the part that owns the symbol.
        /// </exception>
        /// TODO: Write the validation rules.
        public StaffDescriptor MyStaff
        {
            get { return myStaff; }
            set
            {
                ValidateAndThrow("MyStaff", value);

                myStaff = value;
                myStaffAttribute.Value = myStaff.ID.ToString();

                RaisePropertyChanged("MyStaff");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="MyStaff"/> property.
        /// </summary>
        private StaffDescriptor myStaff;
        /// <summary>
        /// Holds the XML attribute that holds the value of <see cref="MyStaff"/> property.
        /// </summary>
        private XmlAttribute myStaffAttribute;

        #endregion
    }
}
